﻿using Notoric.Commanding.CommandBase;
using Notoric.Model.Elements;
using Notoric.Model.Validation;

namespace Notoric.ViewModel
{
    /// <summary>
    /// Implements an undoable command for setting one property on a target <see cref="ModelElement"/>.
    /// </summary>
    /// <remarks>
    /// Input Parameters:
    /// <list type="table">
    /// <listheader>
    /// <term>
    ///     Name (Type)
    /// </term>
    /// <description>
    ///     Description
    /// </description>
    /// </listheader>
    /// <item>
    /// <term>
    ///     Target (<see cref="ViewModelElement"/>)
    /// </term>
    /// <description>
    ///     The view-model element on whose adapted element the property is being set.
    /// </description>
    /// </item>
    /// <item>
    /// <term>
    ///     Property (<see cref="System.String"/>)
    /// </term>
    /// <description>
    ///     The name of the property on the target that is being set.
    /// </description>
    /// </item>
    /// <item>
    /// <term>
    ///     Value (<typeparamref name="T"/>)
    /// </term>
    /// <description>
    ///     The value that is being set to the property.
    /// </description>
    /// </item>
    /// </list>
    /// <para>
    /// The command can execute if and only if all the input parameters are set, the <see cref="Target"/> is not <see langword="null"/>
    /// and the <see cref="Value"/> is valid for the <see cref="Property"/> (<see cref="ModelElement.ValidatePropertyValue"/> returns <see langword="true"/>).
    /// </para>
    /// </remarks>
    /// <typeparam name="T">Type of the property that is being set</typeparam>
    public class SetterCommand<T> : UndoableCommandBase
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="SetterCommand{T}"/> instance.
        /// </summary>
        /// <param name="undoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The command stack on which this command will be put after a successful execution.
        /// </para>
        /// </param>
        /// <param name="redoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The command stack on which this command will be put when it will have been undone.
        /// </para>
        /// </param>
        public SetterCommand(CommandStack undoStack, CommandStack redoStack)
            : base(undoStack, redoStack)
        {
            AddParameter<ViewModelElement>("Target", ParameterDirection.In);
            AddParameter<string>("Property", ParameterDirection.In);
            AddParameter<T>("Value", ParameterDirection.In);
        }

        /// <summary>
        /// Creates a new <see cref="SetterCommand{T}"/> instance.
        /// </summary>
        /// <param name="target">
        /// <para>
        /// Type: <see cref="ViewModelElement"/>
        /// </para>
        /// <para>
        /// The view-model element on whose adapted element the property will be set.
        /// </para>
        /// </param>
        /// <param name="propertyName">
        /// <para>
        /// Type: <see cref="string"/>
        /// </para>
        /// <para>
        /// The name of the property on the <paramref name="target"/>  that will be set.
        /// </para>
        /// </param>
        /// <param name="value">
        /// <para>
        /// Type: <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// The value that will be set to the property.
        /// </para>
        /// </param>
        /// <param name="undoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The command stack on which this command will be put after a successful execution.
        /// </para>
        /// </param>
        /// <param name="redoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The command stack on which this command will be put when it will have been undone.
        /// </para>
        /// </param>
        public SetterCommand(ViewModelElement target, string propertyName, T value, CommandStack undoStack, CommandStack redoStack)
            : this(undoStack, redoStack)
        {
            SetParameterValue<ViewModelElement>("Target", target);
            SetParameterValue<string>("Property", propertyName);
            SetParameterValue<T>("Value", value);
        }

        #endregion

        #region UndoableCommandBase Members

        /// <inheritdoc/>
        protected override void CommandOperation(object parameter)
        {
            Rule[] brokenRules;

            oldValue = (T)Target.AdaptedElement.GetPropertyValue(Property);
            Target.AdaptedElement.ValidateAndSetPropertyValue(Property, Value, out brokenRules);

            Target.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void RedoOperation()
        {
            Rule[] brokenRules;

            Target.AdaptedElement.ValidateAndSetPropertyValue(Property, Value, out brokenRules);

            Target.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void UndoOperation()
        {
            Rule[] brokenRules;

            Target.AdaptedElement.ValidateAndSetPropertyValue(Property, oldValue, out brokenRules);

            Target.FindOwner<Elements.Project>().IsModified = true;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the name of the property that is being set.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="string"/>
        /// </para>
        /// <para>
        /// The name of the property on the <see cref="Target"/> that is being set by this command.<br/>
        /// This property wraps the "Property" parameter of the command.
        /// </para>
        /// </value>
        public string Property
        {
            get { return GetParameterValue<string>("Property"); }
            set { SetParameterValue<string>("Property", value); }
        }

        /// <summary>
        /// Gets or sets the element on which the property is being set.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ViewModelElement"/>
        /// </para>
        /// <para>
        /// The view-model element on whose <see cref="ViewModelElement.AdaptedElement"/> 
        /// the <see cref="Property"/> is being set by this command.<br/>
        /// This property wraps the "Target" parameter of the command.
        /// </para>
        /// </value>
        public ViewModelElement Target
        {
            get { return GetParameterValue<ViewModelElement>("Target"); }
            set { SetParameterValue<ViewModelElement>("Target", value); }
        }

        /// <summary>
        /// Gets or sets the value that is being set.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// The value that is being set to <see cref="Property"/>.
        /// This property wraps the "Value" parameter of the command.
        /// </para>
        /// </value>
        public T Value
        {
            get { return GetParameterValue<T>("Value"); }
            set { SetParameterValue<T>("Value", value); }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the old value of the target property.
        /// </summary>
        private T oldValue;

        #endregion
    }

    /// <summary>
    /// Implements an undoable command for setting two properties on a target <see cref="ModelElement"/>.
    /// </summary>
    /// <remarks>
    /// Input Parameters:
    /// <list type="table">
    /// <listheader>
    /// <term>
    ///     Name (Type)
    /// </term>
    /// <description>
    ///     Description
    /// </description>
    /// </listheader>
    /// <item>
    /// <term>
    ///     Target (<see cref="ViewModelElement"/>)
    /// </term>
    /// <description>
    ///     The view-model element on whose adapted element the property is being set.
    /// </description>
    /// </item>
    /// <item>
    /// <term>
    ///     Property1 (<see cref="System.String"/>)
    /// </term>
    /// <description>
    ///     The name of the first property on the target that is being set.
    /// </description>
    /// </item>
    /// <item>
    /// <term>
    ///     Value1 (<typeparamref name="T1"/>)
    /// </term>
    /// <description>
    ///     The value that is being set to the first property.
    /// </description>
    /// </item>
    /// <item>
    /// <term>
    ///     Property2 (<see cref="System.String"/>)
    /// </term>
    /// <description>
    ///     The name of the second property on the target that is being set.
    /// </description>
    /// </item>
    /// <item>
    /// <term>
    ///     Value1 (<typeparamref name="T2"/>)
    /// </term>
    /// <description>
    ///     The value that is being set to the second property.
    /// </description>
    /// </item>
    /// </list>
    /// <para>
    /// The command can execute if and only if all the input parameters are set, the <see cref="Target"/> is not <see langword="null"/>
    /// and both the <see cref="Value1"/> and <see cref="Value2"/> are valid for the corresponding properties 
    /// (<see cref="ModelElement.ValidatePropertyValue"/> returns <see langword="true"/>).
    /// </para>
    /// </remarks>
    /// <typeparam name="T1">Type of the first property being set</typeparam>
    /// <typeparam name="T2">Type of the second property being set</typeparam>
    public class SetterCommand<T1, T2> : UndoableCommandBase
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="SetterCommand{T1, T2}"/> instance.
        /// </summary>
        /// <param name="undoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The command stack on which this command will be put after a successful execution.
        /// </para>
        /// </param>
        /// <param name="redoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The command stack on which this command will be put when it will have been undone.
        /// </para>
        /// </param>
        public SetterCommand(CommandStack undoStack, CommandStack redoStack)
            : base(undoStack, redoStack)
        {
            AddParameter<ViewModelElement>("Target", ParameterDirection.In);
            AddParameter<string>("Property1", ParameterDirection.In);
            AddParameter<string>("Property2", ParameterDirection.In);
            AddParameter<T1>("Value1", ParameterDirection.In);
            AddParameter<T2>("Value2", ParameterDirection.In);
        }

        #endregion

        #region UndoableCommandBase Members

        /// <inheritdoc/>
        protected override void CommandOperation(object parameter)
        {
            oldValue1 = (T1)Target.AdaptedElement.GetPropertyValue(Property1);
            oldValue2 = (T2)Target.AdaptedElement.GetPropertyValue(Property2);

            Rule[] brokenRules;

            Target.AdaptedElement.ValidateAndSetPropertyValue(Property1, Value1, out brokenRules);
            Target.AdaptedElement.ValidateAndSetPropertyValue(Property2, Value2, out brokenRules);

            Target.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void RedoOperation()
        {
            Rule[] brokenRules;

            Target.AdaptedElement.ValidateAndSetPropertyValue(Property1, Value1, out brokenRules);
            Target.AdaptedElement.ValidateAndSetPropertyValue(Property2, Value2, out brokenRules);

            Target.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void UndoOperation()
        {
            Rule[] brokenRules;

            Target.AdaptedElement.ValidateAndSetPropertyValue(Property1, oldValue1, out brokenRules);
            Target.AdaptedElement.ValidateAndSetPropertyValue(Property2, oldValue2, out brokenRules);

            Target.FindOwner<Elements.Project>().IsModified = true;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the name of the first property that is being set.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The name of the first property on <see cref="Target"/> that is being set by this command.<br/>
        /// This property wraps the "Property1" command parameter.
        /// </para>
        /// </value>
        public string Property1
        {
            get { return GetParameterValue<string>("Property1"); }
            set { SetParameterValue<string>("Property1", value); }
        }

        /// <summary>
        /// Gets or sets the name of the second property that is being set.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The name of the second property on <see cref="Target"/> that is being set by this command.<br/>
        /// This property wraps the "Property2" command parameter.
        /// </para>
        /// </value>
        public string Property2
        {
            get { return GetParameterValue<string>("Property2"); }
            set { SetParameterValue<string>("Property2", value); }
        }

        /// <summary>
        /// Gets or sets the element on which the properties are being set.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ViewModelElement"/>
        /// </para>
        /// <para>
        /// The view-model element on which <see cref="ViewModelElement.AdaptedElement"/> the properties are being set by this command.<br/>
        /// This property wraps the "Target" command parameter.
        /// </para>
        /// </value>
        public ViewModelElement Target
        {
            get { return GetParameterValue<ViewModelElement>("Target"); }
            set { SetParameterValue<ViewModelElement>("Target", value); }
        }

        /// <summary>
        /// Gets or sets the value that is being set to the first property..
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <typeparamref name="T1"/>
        /// </para>
        /// <para>
        /// The value that is being set to the first property.<br/>
        /// This property wraps the "Value1" command parameter.
        /// </para>
        /// </value>
        public T1 Value1
        {
            get { return GetParameterValue<T1>("Value1"); }
            set { SetParameterValue<T1>("Value1", value); }
        }

        /// <summary>
        /// Gets or sets the value that is being set to the second property..
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <typeparamref name="T2"/>
        /// </para>
        /// <para>
        /// The value that is being set to the second property.<br/>
        /// This property wraps the "Value2" command parameter.
        /// </para>
        /// </value>
        public T2 Value2
        {
            get { return GetParameterValue<T2>("Value2"); }
            set { SetParameterValue<T2>("Value2", value); }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the old value of the first property.
        /// </summary>
        private T1 oldValue1;
        /// <summary>
        /// Holds the old value of the second property.
        /// </summary>
        private T2 oldValue2;

        #endregion
    }
}
